package com.example.controller;

import com.example.annotation.RateLimit;
import com.example.exception.BusinessException;
import com.example.util.SecurityUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;

/**
 * 安全文件上传控制器
 */
@Slf4j
@RestController
@RequestMapping("/api/secure-upload")
@Tag(name = "安全文件上传", description = "增强安全的文件上传接口")
@RequiredArgsConstructor
public class SecureFileController {

    private final SecurityUtils securityUtils;

    @Value("${file.upload.path:/tmp/uploads}")
    private String uploadPath;

    @Value("${file.upload.max-size:2097152}") // 2MB
    private long maxFileSize;

    // 允许的文件类型
    private static final Set<String> ALLOWED_IMAGE_TYPES = Set.of(
        "image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"
    );

    // 危险文件扩展名黑名单
    private static final Set<String> DANGEROUS_EXTENSIONS = Set.of(
        "exe", "bat", "cmd", "com", "pif", "scr", "vbs", "js", "jar", "php", "asp", "jsp"
    );

    @PostMapping("/image")
    @Operation(summary = "安全上传图片")
    @RateLimit(key = "upload_image", type = RateLimit.LimitType.USER, limit = 20, window = 3600, message = "图片上传过于频繁")
    public ResponseEntity<Map<String, Object>> uploadImage(
            @RequestParam("file") MultipartFile file,
            Authentication authentication) {
        
        return uploadFile(file, "image", ALLOWED_IMAGE_TYPES, authentication);
    }

    @PostMapping("/avatar")
    @Operation(summary = "上传头像")
    @RateLimit(key = "upload_avatar", type = RateLimit.LimitType.USER, limit = 5, window = 3600, message = "头像上传过于频繁")
    public ResponseEntity<Map<String, Object>> uploadAvatar(
            @RequestParam("file") MultipartFile file,
            Authentication authentication) {
        
        // 头像有更严格的限制
        if (file.getSize() > 1048576) { // 1MB
            throw new BusinessException("FILE_TOO_LARGE", "头像文件大小不能超过1MB");
        }
        
        return uploadFile(file, "avatar", ALLOWED_IMAGE_TYPES, authentication);
    }

    /**
     * 通用文件上传方法
     */
    private ResponseEntity<Map<String, Object>> uploadFile(
            MultipartFile file, 
            String category, 
            Set<String> allowedTypes, 
            Authentication authentication) {
        
        try {
            // 基础验证
            validateFile(file, allowedTypes);
            
            // 生成安全的文件名
            String originalFilename = file.getOriginalFilename();
            String safeFilename = generateSafeFilename(originalFilename);
            
            // 创建目录结构
            String userDir = authentication.getName();
            Path categoryPath = Paths.get(uploadPath, category, userDir);
            Files.createDirectories(categoryPath);
            
            // 保存文件
            Path filePath = categoryPath.resolve(safeFilename);
            Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
            
            // 生成访问URL
            String fileUrl = String.format("/api/files/%s/%s/%s", category, userDir, safeFilename);
            
            log.info("文件上传成功: user={}, category={}, filename={}, size={}", 
                    authentication.getName(), category, safeFilename, file.getSize());
            
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("message", "文件上传成功");
            response.put("data", Map.of(
                "filename", safeFilename,
                "originalFilename", originalFilename,
                "url", fileUrl,
                "size", file.getSize(),
                "contentType", file.getContentType()
            ));
            
            return ResponseEntity.ok(response);
            
        } catch (IOException e) {
            log.error("文件上传IO异常: {}", e.getMessage(), e);
            throw new BusinessException("UPLOAD_ERROR", "文件上传失败");
        } catch (Exception e) {
            log.error("文件上传异常: {}", e.getMessage(), e);
            if (e instanceof BusinessException) {
                throw e;
            }
            throw new BusinessException("UPLOAD_ERROR", "文件上传失败");
        }
    }

    /**
     * 验证文件
     */
    private void validateFile(MultipartFile file, Set<String> allowedTypes) {
        // 检查文件是否为空
        if (file.isEmpty()) {
            throw new BusinessException("EMPTY_FILE", "文件不能为空");
        }
        
        // 检查文件大小
        if (file.getSize() > maxFileSize) {
            throw new BusinessException("FILE_TOO_LARGE", 
                String.format("文件大小不能超过%dMB", maxFileSize / 1024 / 1024));
        }
        
        // 检查文件名
        String originalFilename = file.getOriginalFilename();
        if (!StringUtils.hasText(originalFilename)) {
            throw new BusinessException("INVALID_FILENAME", "文件名无效");
        }
        
        // 检查文件名安全性
        if (!securityUtils.isSafeFilename(originalFilename)) {
            throw new BusinessException("UNSAFE_FILENAME", "文件名包含非法字符");
        }
        
        // 检查文件扩展名
        String extension = getFileExtension(originalFilename).toLowerCase();
        if (DANGEROUS_EXTENSIONS.contains(extension)) {
            throw new BusinessException("DANGEROUS_FILE", "不允许上传此类型的文件");
        }
        
        // 检查MIME类型
        String contentType = file.getContentType();
        if (contentType == null || !allowedTypes.contains(contentType)) {
            throw new BusinessException("INVALID_FILE_TYPE", 
                "不支持的文件类型: " + contentType);
        }
        
        // 检查文件头（魔数）验证
        if (!validateFileHeader(file, contentType)) {
            throw new BusinessException("INVALID_FILE_CONTENT", "文件内容与类型不匹配");
        }
    }

    /**
     * 生成安全的文件名
     */
    private String generateSafeFilename(String originalFilename) {
        if (!StringUtils.hasText(originalFilename)) {
            return "unknown_" + System.currentTimeMillis();
        }
        
        // 获取文件扩展名
        String extension = getFileExtension(originalFilename);
        
        // 生成UUID作为文件名
        String uuid = UUID.randomUUID().toString().replace("-", "");
        
        // 添加时间戳
        String timestamp = String.valueOf(System.currentTimeMillis());
        
        return uuid + "_" + timestamp + (StringUtils.hasText(extension) ? "." + extension : "");
    }

    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String filename) {
        if (!StringUtils.hasText(filename)) {
            return "";
        }
        
        int lastDotIndex = filename.lastIndexOf('.');
        if (lastDotIndex > 0 && lastDotIndex < filename.length() - 1) {
            return filename.substring(lastDotIndex + 1);
        }
        
        return "";
    }

    /**
     * 验证文件头（魔数）
     */
    private boolean validateFileHeader(MultipartFile file, String contentType) {
        try {
            byte[] header = new byte[8];
            int bytesRead = file.getInputStream().read(header);
            
            if (bytesRead < 4) {
                return false;
            }
            
            // 检查常见图片格式的魔数
            if (contentType.startsWith("image/")) {
                return validateImageHeader(header, contentType);
            }
            
            return true; // 其他类型暂时通过
            
        } catch (IOException e) {
            log.error("读取文件头失败: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 验证图片文件头
     */
    private boolean validateImageHeader(byte[] header, String contentType) {
        switch (contentType) {
            case "image/jpeg":
            case "image/jpg":
                return header[0] == (byte) 0xFF && header[1] == (byte) 0xD8;
            case "image/png":
                return header[0] == (byte) 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47;
            case "image/gif":
                return (header[0] == 0x47 && header[1] == 0x49 && header[2] == 0x46);
            case "image/webp":
                return header[0] == 0x52 && header[1] == 0x49 && header[2] == 0x46 && header[3] == 0x46;
            default:
                return false;
        }
    }

    @GetMapping("/info")
    @Operation(summary = "获取上传配置信息")
    public ResponseEntity<Map<String, Object>> getUploadInfo() {
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("data", Map.of(
            "maxFileSize", maxFileSize,
            "maxFileSizeMB", maxFileSize / 1024 / 1024,
            "allowedImageTypes", ALLOWED_IMAGE_TYPES
        ));
        
        return ResponseEntity.ok(response);
    }
}
